﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq.Expressions;
using System.Reflection.Emit;
using System.Linq;

namespace FastData.Mapping
{
    public class DataMapper
    {
        private static ConcurrentDictionary<string, object> _mappers = new ConcurrentDictionary<string, object>();

        private static void Add<T>(Func<string[], object[], T> func)
        {

        }

        private static Func<object[], T> GetMapper<T>(Type type, string[] names)
        {
            string key = string.Join(",", names) + "," + type.ToString();

            object mapper;
            if (!_mappers.TryGetValue(key, out mapper))
            {
                mapper = CreateMapper<T>(type, names);
                _mappers[key] = mapper;
            }
            return mapper as Func<object[], T>;
        }

        private static Func<object[], T> CreateMapper<T>(Type type, string[] names)
        {
            var typeInfo = DataTypeInfo.GetInfo(type);
            var constructor = type.GetConstructor(new Type[] { });

            var values = Expression.Parameter(typeof(object[]), "values");
            var result = Expression.Parameter(type, "result");
            var value = Expression.Parameter(typeof(object), "value");
            var label = Expression.Label(type);

            List<Expression> body = new List<Expression>();
            body.Add(Expression.Assign(result, Expression.New(constructor)));
            for (int i = 0; i < names.Length; i++)
            {
                var property = typeInfo.Properties.Where(info => info.Name.Equals(names[i])).FirstOrDefault();
                if (property != null)
                {
                    body.Add(Expression.Assign(value, Expression.ArrayIndex(values, Expression.Constant(i))));
                    body.Add(Expression.IfThenElse(Expression.Equal(value, Expression.Constant(DBNull.Value)),
                        Expression.Assign(Expression.Property(result, property.Name), Expression.Convert(Expression.Constant(null), property.PropertyType)),
                        Expression.Assign(Expression.Property(result, property.Name), Expression.Convert(value, property.PropertyType))));
                }
            }
            body.Add(Expression.Label(label, result));

            return Expression.Lambda<Func<object[], T>>(Expression.Block(type, new ParameterExpression[] { result, value }, body), values).Compile();
        }

        internal static IEnumerable<T> Map<T>(List<object[]> rows, string[] names)
        {
            var type = typeof(T);

            if (type == typeof(object))
            {
                int fieldCount = names.Length;
                foreach (var values in rows)
                {
                    IDictionary<string, object> entity = new ExpandoObject();
                    for (int i = 0; i < fieldCount; i++)
                    {
                        entity.Add(names[i], values[i].IfDBNull(null));
                    }
                    yield return (T)entity;
                }
            }
            else
            {
                var mapper = GetMapper<T>(type, names);
                int rowCount = rows.Count;
                for (int i = 0; i < rowCount; i++)
                {
                    yield return mapper(rows[i]);
                }
            }
        }

    }
}
